//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

// Includes
#include <mattise.h>
#include <stdint.h>
#include <exec/elf.h>
#include <mem/util.h>
#include <mem/page.h>
#include <kernio/io.h>
#include <process/common.h>
#include <core/intrcommon.h>

// Macros

// offset from himem of a page table
#define PT_OFF( v ) ((v>>22)<<12)

// whether to do stack traces and things like that or not
#define PRETTY_EXCEPTION_OUTPUT

// whether to use refcounting for garbage collection
#define USE_REFCOUNT_GC 0

// macro to make unused work properly
#if USE_REFCOUNT_GC
#define PAGING_MAYBE_UNUSED(a) (a)
#else
#define PAGING_MAYBE_UNUSED(a) UNUSED(a)
#endif

// Variables

// the kernel page directory
uint32_t* kerneldir;

// bytemap holding refcount for mapped pages
#if USE_REFCOUNT_GC
#define REFCOUNT_TYPE uint16_t
#define MAX_REFCOUNT 0xffff
volatile REFCOUNT_TYPE page_refcount[0xFFFFF];
#endif

// Functions

// gets the kernel page directory (physical)
uint32_t GetKernelDirectory()
{
	return (uint32_t) kerneldir;
}

// loads CR3 with a specific address
void kLoadCR3( uint32_t addr )
{
	if(kGetCR3() == addr)
	{
		//dprintf("Not reloading CR3\n");
	}
	else
	{
		//dprintf("loading CR3 with %x\n", addr);
		asm volatile("mov %%eax, %%cr3" : : "a" (addr));
		//dprintf("CR3 is now %x\n", addr);
	}
}

// gets the value of CR3
uint32_t kGetCR3()
{
	uint32_t ret;
	asm volatile("mov %%cr3, %%eax" : "=a" (ret));
	return ret;
}

// destroys the mapping for a specific page
void kDestroyPageMapping(uint32_t pagedir, uint32_t page, uint32_t free_phys)
{
	uint32_t* ppagedir = 0;
	page &= ~0xFFF;
	if(pagedir == 0xfffff000 || pagedir == kGetCR3())
	{
		ppagedir = (uint32_t*) 0xfffff000;

		// check for a valid page table
		if( ( ppagedir[page >> 22] & ~0xFFF ) == 0 )
			return; // invalid page table

		// valid page table, so destroy the mapping
		uint32_t* pagetab = (uint32_t*) ((0xFFC00000 + (PT_OFF(page))));
		uint32_t phys = pagetab[page << 10 >> 22] & ~0xFFF;
		pagetab[page << 10 >> 22] = 0;

		// free the physical page too
		if(free_phys)
		{
			kDecrRefCount(phys);
			if(kGetRefCount(phys) == 0)
				kFreePages((uint32_t*) phys);
		}
	}
	else
	{
		MapPage( 0xfffff000, 0xffba0000, pagedir, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );
		ppagedir = (uint32_t*) 0xffba0000;

		// check for a valid page table
		if( ( ppagedir[page >> 22] & ~0xFFF ) == 0 )
			return; // invalid table

		MapPage( 0xfffff000, 0xffbb0000, ppagedir[page >> 22] & ~0xFFF, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );
		uint32_t* pagetab = (uint32_t*) 0xffbb0000;

		// destroy the mapping
		pagetab = 0;
	}
}

// for debugging, basically allows a page fault to return (to get the call stack, basically)
int pagefault_just_continue_allowed = 0;

// page fault handler
void kPageFaultHandler( struct stack_layout* stack )
{
    // grab the page directory and CR2 value
    uint32_t cr3, cr2;
    asm volatile( "mov %%cr3,%0" : "=r" (cr3) );
    asm volatile( "mov %%cr2,%0" : "=r" (cr2) );

    // print out some information
	if(!pagefault_just_continue_allowed)
	{
		printf( "\n\n== PAGE FAULT ==\n" );
		printf( "EIP=0x%x, CS=0x%x, PID=%d\n", stack->eip, stack->cs, getpid() );
		printf( "CR3=0x%x, CR2=0x%x\n\n", cr3, cr2 );
	}

    // output stuff to the debug console
    dprintf( "Extra Debugging Information:\n" );
	dprintf( "EFLAGS: 0x%x\n", stack->eflags );
    dprintf( "CR3: 0x%x, CR2: 0x%x\n", cr3, cr2 );
    dprintf( "error code: 0x%x\n", stack->ecode );
    dprintf( "EIP: %x\n", stack->eip );

    // print the general purpose registers
    dprintf( "GPRs:\n" );
    dprintf( "EAX: %x, EBX: %x, ECX: %x, EDX: %x, ESI: %x, EDI: %x\n", stack->eax, stack->ebx, stack->ecx, stack->edx, stack->esi, stack->edi );
    dprintf( "ESP: %x, EBP: %x\n", stack->esp, stack->ebp );

#ifdef PRETTY_EXCEPTION_OUTPUT

	// don't print pretty output if the location is invalid
	if(stack->eip != 0)
	{
		// print out as well the symbol name of the function we were executing in
		dprintf( "\nFault occured at " );
		getkernsym( stack->eip );
		dprintf( "\n" );
	}

	// stack base and instruction pointer
	uintptr_t base = stack->ebp;
	uintptr_t eip = stack->eip;

	// check that EBP is valid
	if( cr2 != stack->ebp && stack->ebp )
	{
		// work through 8 frames
		size_t i = 0;

		// print info
		dprintf( "Page fault with EIP=%x, EBP=%x\nCall stack:\n", eip, base );

		// print it out!
		dprintf( "%d: ", i++ );
		getkernsym( eip );
		if(cr2 != eip)
			dprintf( " eip=%x [op=%x], ebp=%x\n", eip, *((uint32_t*) eip), base );
		else
			dprintf( " eip=%x, ebp=%x\n", eip, base );

		// keep going through more and more!
		while( i < 16 )
		{
			// stop if the base is unmapped
			if( IsMapped( cr3, base ) == 0 )
				break;

			// get the EBP pointer
			uintptr_t nextbase = *((uintptr_t*) base);

			// check that it's valid
			if( nextbase == 0 ) break;

			// find the return address
			eip = *((uintptr_t*) (base + sizeof( uintptr_t )));

			// set the next EBP
			base = nextbase;

			// print it
			dprintf( "%d: ", i );
			getkernsym( eip );
			dprintf( " eip=%x, ebp=%x\n", eip, base );

			// next frame
			i++;
		}
	}

#endif

	// restore interrupts
	asm volatile("sti");

	// return if we can
	if(pagefault_just_continue_allowed)
	{
		stack->eip += 6;
		return;
	}

	// kill the task that caused the fault
	if( getpid() > 3 )
	{
		dprintf("Killing and continuing\n");
		kill( getpid() );
		kSchedule( 0 );
	}

	dprintf("Not attempting to continue\n");

    // loop forever
    while( 1 );
}

// adjusts the reference counts
void kIncrRefCount( uint32_t PAGING_MAYBE_UNUSED(page) )
{
#if USE_REFCOUNT_GC
	// halt until we get a valid reference count
	//dprintf( "ref count for page %x is %x\n", page, page_refcount[page / 0x1000] );
	while( page_refcount[page / 0x1000] == MAX_REFCOUNT )
	{
		kSchedule( 0 );
		asm volatile( "pause" );
	}
	page_refcount[page / 0x1000]++;
#endif
}

// reduces the reference count
void kDecrRefCount( uint32_t PAGING_MAYBE_UNUSED(page) )
{
#if USE_REFCOUNT_GC
	if( page_refcount[page / 0x1000] )
		page_refcount[page / 0x1000]--;
#endif
}

// gets the reference count for a page
uint8_t kGetRefCount( uint32_t PAGING_MAYBE_UNUSED(page) )
{
#if USE_REFCOUNT_GC
	return page_refcount[page / 0x1000];
#else
	return 0;
#endif
}

// this symbol sits at the end of the kernel in ram
extern char __end;

// sets up paging
void kInitPaging()
{
    // allocate space for a page directory and a page table
    kerneldir = (uint32_t*) kAllocatePages( 1 );

	// zero the kernel directory
	memset(kerneldir, 0, 0x1000);

    // zero out the reference count array
#if USE_REFCOUNT_GC
    memset( (void*) page_refcount, 0, 0xfffff * sizeof( REFCOUNT_TYPE ) );
#endif

	// the page flags to use
	uint32_t pageflags = PF_GLOBAL | PF_PRESENT | PF_KERNELPAGE | PF_READWRITE | PF_IMMUTABLE;

	// find out the end of the kernel space
#if USE_REFCOUNT_GC
	uint32_t kernel_virt_end = 0x800000; // 8 MB
	//(((uint32_t) &__end) & ~0xFFF) + 0x1000;
#else
	uint32_t kernel_virt_end = 0x400000; // 4 MB
#endif

	// map in the kernel 1:1
	uint32_t page = 0;
	for(page = 0; page <= kernel_virt_end; page += 0x1000)
	{
		// check to see if we need to allocate a page table first
		if((kerneldir[page >> 22] & ~0xFFF) == 0)
			kerneldir[page >> 22] = ((uint32_t) kAllocatePages( 1 )) | pageflags;
		if((kerneldir[page >> 22] & ~0xFFF) == 0)
		{
			dprintf("[PAGE ] couldn't create page table\n");
			while(1); // won't map :S
		}

		// set the page
		if(page == 0)
			((uint32_t*) (kerneldir[page >> 22] & ~0xFFF))[page << 10 >> 22] = 0;
		else
			((uint32_t*) (kerneldir[page >> 22] & ~0xFFF))[page << 10 >> 22] = page | pageflags;

		// increment the refcount
		kIncrRefCount(page);
	}

    /*uint32_t* pagetab = (uint32_t*) kAllocatePages( 1 );
    uint32_t* pagetab2 = (uint32_t*) kAllocatePages( 1 );
    uint32_t* pagetab3 = (uint32_t*) kAllocatePages( 1 );


    // the kernel directory and its page tables are in use!
    kIncrRefCount( (uint32_t) kerneldir );
    kIncrRefCount( (uint32_t) pagetab );
    kIncrRefCount( (uint32_t) pagetab2 );
    kIncrRefCount( (uint32_t) pagetab3 );

    // setup the page table to map the first 8 MB 1:1
    size_t i;
    for( i = 0; i < 1024; i++ )
    {
        pagetab[i] = (i * 0x1000) | pageflags; // supervisor, read/write, present, put in our flags into the "available" section
        kIncrRefCount( i * 0x1000 );
    }
    for( i = 1024; i < 2048; i++ )
    {
        pagetab2[i - 1024] = (i * 0x1000) | pageflags; // supervisor, read/write, present, put in our flags into the "available" section
        kIncrRefCount( i * 0x1000 );
    }
    for( i = 2048; i < 3072; i++ )
    {
        pagetab3[i - 2048] = (i * 0x1000) | pageflags; // supervisor, read/write, present, put in our flags into the "available" section
        kIncrRefCount( i * 0x1000 );
    }

    // null pointers should fault - the refcount must remain though
    pagetab[0] = 0;

    // setup the page directory
    kerneldir[0] = ((uint32_t) pagetab) | pageflags;
    kerneldir[1] = ((uint32_t) pagetab2) | pageflags;
    kerneldir[2] = ((uint32_t) pagetab3) | pageflags;*/

    // map the page directory to itself
    kerneldir[1023] = ((uint32_t) kerneldir) | pageflags;

    // setup the custom page fault handler
    kInstallCustomIntrHandler( 14, kPageFaultHandler );

    // fill CR3
    asm volatile( "mov %0,%%cr3" : : "r" (kerneldir) );

    // enable paging
    uint32_t cr0;
    asm volatile( "mov %%cr0,%0" : "=r" (cr0) );
    cr0 |= 0x80000000;
    asm volatile( "mov %0,%%cr0" : : "r" (cr0) );
}

// clones a page directory (recopies everything)
void CloneDirectory( uint32_t dir, uint32_t destdir )
{
	// we're toying with process data so don't let us be interrupted
	uint32_t intstate;
	asm volatile( "pushf; pop %0; cli" : "=r" (intstate) );

    // grab pointers to both arguments
    uint32_t* pDir = (uint32_t*) dir;
    uint32_t* pDestDir = (uint32_t*) destdir;

    // map in the directories
    MapPage( 0xfffff000, 0xffba0000, (uint32_t) dir, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );
    MapPage( 0xfffff000, 0xffbb0000, (uint32_t) destdir, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );

    // update the pointers
    pDir = (uint32_t*) 0xffba0000;
    pDestDir = (uint32_t*) 0xffbb0000;

    // start by copying page table addresses with flags
    size_t i;
    for( i = 0; i < 1024; i++ )
    {
		pDestDir[i] = pDir[i];
    }

    // and then copy page tables
    for( i = 0; i < 1023; i++ )
    {
        // check to see if there is a page table
        if( (pDestDir[i] & ~0xFFF) == 0 )
		{
            continue; // keep going, don't worry about copying
		}
		
		// the new page table
		uint32_t* newpt = 0, *newpt_mapped = 0;

        // grab a pointer to the old page table
        uint32_t* oldpt = (uint32_t*) (pDestDir[i] & ~0xFFF);
        MapPage( 0xfffff000, 0xffbd0000, (uint32_t) oldpt, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );

        // grab the flags
        uint32_t ptflags = pDestDir[i] & 0xFFF;
		if( ptflags & PF_KERNELPAGE )
		{
			// still increment the reference count for each page (and the page table)
			kIncrRefCount((uint32_t) oldpt);
			newpt_mapped = (uint32_t*) 0xffbd0000;
		}
		else
		{
			// allocate space for the new page table - IF IT FAILED we'll get 0 as the physical address
			newpt = kAllocatePages( 1 );
			if( !newpt )
				continue;

			// map in the new page table
			MapPage( 0xfffff000, 0xffbc0000, (uint32_t) newpt, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );
			newpt_mapped = (uint32_t*) (0xffbc0000);

			// new page table is referenced
			kIncrRefCount( (uint32_t) newpt );

			// copy them across
			memcpy( newpt_mapped, (void*) 0xffbd0000, 0x1000 );

			// and then set the new address
			pDestDir[i] = ((uint32_t) newpt) | ptflags;
		}

        // go through this page table, copying across all the pages
        size_t z;
        for( z = 0; z < 1024; z++ )
        {
        	// get the page
        	uint32_t pg = newpt_mapped[z] & ~0xFFF;

        	// check that it's a valid one to copy
        	if( pg == 0 )
				continue;

        	// get the flags
        	uint32_t pgflags = newpt_mapped[z] & 0xFFF;

        	// don't copy kernel pages
        	if( pgflags & PF_KERNELPAGE )
        	{
        		// another reference to that page (though no copy)
        		kIncrRefCount( pg );
        	}

        	// only do the copy if we can
        	else if( pgflags & PF_READWRITE )
        	{
				// allocate space to copy the physical data across
				uint32_t* newspace = (uint32_t*) kAllocatePages( 1 );

				// check for a valid pointer
				if( !newspace )
					continue; // out of room?

				// map them both in
				MapPage( 0xfffff000, 0xffbe0000, pg, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );
				MapPage( 0xfffff000, 0xffbf0000, (uint32_t) newspace, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );

				// reference the new space
				kIncrRefCount( (uint32_t) newspace );

				// copy the data across
				memcpy( (void*) 0xffbf0000, (void*) 0xffbe0000, 0x1000 );

				//dprintf( "newpt_mapped = %x [z=%d]\n", newpt_mapped, z );

				// and map into the page table
				newpt_mapped[z] = ((uint32_t) newspace) | pgflags;
        	}
        }
    }

    // map in the page directory
    pDestDir[1023] = ((uint32_t) destdir) | (PF_USER | PF_READWRITE | PF_PRESENT | PF_KERNELPAGE);

	// re-enable interrupts
	if( intstate & 0x200 )
		asm volatile( "sti" );
}

// gets a physical address for a page from a page directory
uint32_t GetPhysicalAddress( uint32_t pagedir, uint32_t page )
{
	// map the page directory address into the parent address space
	uint32_t* ppagedir = 0;
	if(pagedir != 0xfffff000)
	{
		MapPage( 0xfffff000, 0xffba0000, pagedir, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );
		ppagedir = (uint32_t*) 0xffba0000;

		// find out which check for a valid page table
		if( ( ppagedir[page >> 22] & ~0xFFF ) == 0 )
			return 0; // invalid mapping

		// return the page
		return ((uint32_t*) (ppagedir[page >> 22] & ~0xFFF))[page << 10 >> 22] & ~0xFFF;
	}
	else
	{
		ppagedir = (uint32_t*) 0xfffff000;

		// find out which check for a valid page table
		if( ( ppagedir[page >> 22] & ~0xFFF ) == 0 )
			return 0; // invalid mapping
		uint32_t* pagetab = (uint32_t*) ((0xFFC00000 + (PT_OFF(page))));
		return pagetab[page << 10 >> 22] & ~0xFFF;
	}

	// shouldn't reach here
	return 0;
}

// gets the flags for a specific page from a page directory
uint32_t GetPageFlags( uint32_t pagedir, uint32_t page )
{
	// map the page directory address into the parent address space
	MapPage( 0xfffff000, 0xffba0000, pagedir, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );
	uint32_t* ppagedir = (uint32_t*) 0xffba0000;

	// find out which check for a valid page table
	if( ( ppagedir[page >> 22] & ~0xFFF ) == 0 )
		return 0; // invalid mapping

	// return the page
	return ((uint32_t*) (ppagedir[page >> 22] & ~0xFFF))[page << 10 >> 22] & 0xFFF;
}

// globals for using in IsMapped
uint32_t global_check_page = 0;
char global_check_ismapped = 0;

// builtin interrupt handler to permit checking mappings
void tmp_pf_handler( struct stack_layout* stk )
{
    // grab the page directory and CR2 value too
    uint32_t cr3, cr2;
    asm volatile( "mov %%cr3,%0" : "=r" (cr3) );
    asm volatile( "mov %%cr2,%0" : "=r" (cr2) );

    // if CR2 == the check page, it's unmapped
    if( cr2 == global_check_page )
		global_check_ismapped = 0;
	// otherwise it's a legitimate PF
	else
		kPageFaultHandler( stk );
}

// checks to see if a page is mapped
// TODO: MAKE THIS FASTER!
char IsMapped( uint32_t pagedir, uint32_t page )
{
    // read the address in CR3 (ie, the current loaded page directory)
    uint32_t cr3;
    asm volatile( "mov %%cr3,%0" : "=r" (cr3) );

	// if it's the current page directory...
	if(pagedir == cr3 || pagedir == 0xfffff000)
	{
		// get a pointer to the page directory
		uint32_t* ppagedir = (uint32_t*) 0xFFFFF000;

		// find out if a page table exists
		uint32_t pagetabaddr = ppagedir[page >> 22] & ~0xFFF;
		uint32_t ptflags = ppagedir[page >> 22] & 0xFFF;
		if( pagetabaddr == 0 || !(ptflags & PF_PRESENT) )
			return 0; // not mapped
		uint32_t* pagetab = (uint32_t*) ((0xFFC00000 + (PT_OFF(page))));

		// get the page flags
		uint32_t pgflags = pagetab[page << 10 >> 22] & 0xFFF;
		if(pgflags & PF_PRESENT)
			return 1;

		// not mapped
		return 0;
	}

    // install a new page fault handler
	kInstallCustomIntrHandler( 14, tmp_pf_handler );

    // load up a new CR3
    asm volatile( "mov %0,%%cr3" : : "r" (pagedir) );

    // try a read from the page
    global_check_page = page;
    global_check_ismapped = 1;
    uint32_t UNUSED(deadbeef) = *((uint32_t*) page);

    // load the old CR3
    asm volatile( "mov %0,%%cr3" : : "r" (cr3) );

    // reset the page fault handler
	kInstallCustomIntrHandler( 14, kPageFaultHandler );

	// all done
	return global_check_ismapped;
}

// changes the mapping of a physical page - if new_phys is zero then the physical
// address is NOT updated
void ChangeMapping( uint32_t paddr, uint32_t vaddr, uint32_t new_phys, uint32_t flags )
{
	// ensure the pages are page-aligned
	vaddr &= ~0xFFF;
	new_phys &= ~0xFFF;
	flags &= 0xFFF;

	// are we working in the CURRENT page directory?
	if( paddr == 0xFFFFF000 )
	{
        // get a pointer to the page directory
        uint32_t* ppagedir = (uint32_t*) 0xFFFFF000;

        // find out if a page table exists
        uint32_t pagetabaddr = ppagedir[vaddr >> 22] & ~0xFFF;
		if( pagetabaddr == 0 )
			return; // we're only changing, not mapping
		uint32_t* pagetab = (uint32_t*) ((0xFFC00000 + (PT_OFF(vaddr))));

        // set it in the page table
		if( new_phys != 0 )
		{
			pagetab[vaddr << 10 >> 22] = new_phys | flags;
		}
		else
		{
			uint32_t old_phys = pagetab[vaddr << 10 >> 22] & ~0xFFF;
			pagetab[vaddr << 10 >> 22] = old_phys | flags;
		}

		// invalidate the mapping
		asm volatile ("invlpg (%%eax)" : : "a" (vaddr) );
	}
	else
	{
		// TODO: write this
		//dprintf("%s with a different pagedir\n", __FUNCTION__);
	}
}

// invalidates a mapping
inline void invlpg(uint32_t pg)
{
    // invalidate the mapping
    asm volatile ("invlpg (%%eax)" : : "a" (pg) );
}

// maps a page into a page directory
void MapPage( uint32_t pagedir, uint32_t page, uint32_t phys, uint32_t flags )
{
    // fix incoming data
    page &= ~0xFFF;
    phys &= ~0xFFF;

    // read the address in CR3 (ie, the current loaded page directory)
    uint32_t cr3;
    asm volatile( "mov %%cr3,%0" : "=r" (cr3) );

	// check the flags first up
	uint32_t pagetab_flags = flags;
	if(!(flags & PF_READWRITE))
		pagetab_flags |= PF_READWRITE;

    // if it's equal, work with the virtual address (if not, work with the given address)
    if( cr3 == pagedir || pagedir == 0xFFFFF000 )
    {
        // get a pointer to the page directory
        uint32_t* ppagedir = (uint32_t*) 0xFFFFF000;

        // find out if a page table exists
        uint32_t pagetabaddr = ppagedir[page >> 22] & ~0xFFF;

        // create it if not
        if( pagetabaddr == 0 )
        {
            // allocate space and enter it in
            ppagedir[page >> 22] = ((uint32_t) kAllocatePages( 1 )) | flags;

            // load the new page table address
            pagetabaddr = ppagedir[page >> 22] & ~0xFFF;

			// check that it's valid
			if( pagetabaddr != 0 )
			{
				// the page table address is now in use
				kIncrRefCount( pagetabaddr );

				// invalidate the mapping of the page table
				//asm volatile ("invlpg (%%eax)" : : "a" (cr3) );
				//asm volatile ("invlpg (%%eax)" : : "a" (pagetabaddr) );
			}
        }
        if( pagetabaddr == 0 )
        {
        	dprintf( "[MapPage] Mapping %x to %x [flags=%x] failed!", page, phys, flags );
            return; // out of memory, perhaps?
        }

        // check the page table's flags - if invalid update
        uint32_t ptflags = ppagedir[page >> 22] & 0xFFF;
        if( ptflags & PF_IMMUTABLE && (!(flags & PF_IMMUTABLE)) )
			return;
        if( (ptflags & 0xFFF) != (flags & 0xFFF) )
			ptflags = flags;
        ppagedir[page >> 22] = (ppagedir[page >> 22] & ~0xFFF) | ptflags;

        // now we have a valid page table, use it
        uint32_t* pagetab = (uint32_t*) ((0xFFC00000 + (PT_OFF(page)))); // & ~0xFFF)));

		// invalidate the mapping of the page table
		asm volatile ("invlpg (%%eax)" : : "a" (pagetab) );

        // set the physical address
        if( ( pagetab[page << 10 >> 22] & 0xFFF ) & PF_IMMUTABLE )
			return;

        pagetab[page << 10 >> 22] = phys | flags;
    }
    else
    {
        // get a pointer to the page directory
        uint32_t* ppagedir = (uint32_t*) pagedir;

        // map the page directory address into the parent address space
		MapPage( 0xfffff000, 0xffba0000, pagedir, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );
		ppagedir = (uint32_t*) 0xffba0000;

        // find out if a page table exists
        uint32_t pagetabaddr = ppagedir[page >> 22] & ~0xFFF;

        // create it if not
        if( pagetabaddr == 0 )
        {
            // allocate space and enter it in
            ppagedir[page >> 22] = ((uint32_t) kAllocatePages( 1 )) | flags;

            // load the new page table address
            pagetabaddr = ppagedir[page >> 22] & ~0xFFF;

			// check that it's valid
			if( pagetabaddr != 0 )
			{
				// the page table address is now in use
				kIncrRefCount( pagetabaddr );

				// invalidate the mapping of the page table
				//asm volatile ("invlpg (%%eax)" : : "a" (cr3) );
				//asm volatile ("invlpg (%%eax)" : : "a" (pagetabaddr) );
			}
        }
        if( pagetabaddr == 0 )
        {
        	dprintf( "[MapPage] Mapping %x to %x [flags=%x] failed!", page, phys, flags );
            return; // out of memory, perhaps?
        }

        // check the page table's flags - if invalid update
        uint32_t ptflags = ppagedir[page >> 22] & 0xFFF;
        if( ptflags != flags )
            ptflags = flags;
        ppagedir[page >> 22] = (ppagedir[page >> 22] & ~0xFFF) | ptflags;

        // map the page table
        MapPage( 0xFFFFF000, /*pagetabaddr*/ 0x90000000, pagetabaddr, PF_PRESENT | PF_READWRITE | PF_USER );

        // perform the mapping
        ((uint32_t*) 0x90000000)[page << 10 >> 22] = phys | flags;
		//((uint32_t*) (ppagedir[page >> 22] & ~0xFFF))[page << 10 >> 22] = phys | flags;
    }

    // another reference!
    kIncrRefCount( phys );

    // invalidate the mapping
    asm volatile ("invlpg (%%eax)" : : "a" (page) );
}
